/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package org.grnt.buddy;

import java.lang.ref.WeakReference;
import java.util.TreeSet;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.grnt.common.Function;
import org.grnt.common.OptStatBean.OptStateException;
import org.grnt.common.Travelor;
import org.grnt.common.TwoTuple;
import static org.grnt.buddy.BuddiesContext.*;

/**
 *
 * This class is a function gather. All these functions implement all semantic of
 * buddy's options.
 * @author sayid
 */
public class BuddiesTreeControler {

	/**
	 *
	 * This class is a decorator of Function which for a particular purpose for
	 * when doing at traveling buddies tree.
	 * @param <R> Type of return value.
	 */
	public static interface BuddiesTravelorFunction<R> extends Function<BuddyDecorator, R>{}
	/**
	 *
	 * This class is a decorator of BuddiesTravelor class for a paricular purpose for
	 * traveling buddies tree.
	 */
	public static interface BuddiesTravelor extends Travelor<Long, BuddyDecorator>{}

	/**
	 *
	 * Gat a buddy's alias as abstract path "xxx@root:/blah/blah..." by a buddy id.
	 * @param BuddyId Buddy id.
	 * @return Buddy's alias as abstract path.
	 */
	public static String absAlias(Long BuddyId){
		if (! getBuddies().containsKey(BuddyId)) {
			return null;
		}

		Buddies buddies = getBuddies();
		BuddyBean buddy = buddies.get(BuddyId).getBuddyBean();
		String result = buddy.getAlias();
		
		for (Long parentId = buddy.getParentId()
				; (parentId != null) && (parentId != 0)
				; parentId = buddy.getParentId()){
			buddy = buddies.get(parentId).getBuddyBean();
			result = buddy.getAlias() + "/" + result;
		}
		return result;
	}

	/**
	 *
	 * Operations on context after add a user.
	 * @param user Object of UserBean;
	 * @param root Buddy id of the user's root group buddy.
	 * @throws org.grnt.buddy.BuddiesTreeControler.BuddiesCtlException
	 */
	public static void addUser(UserBean user, BuddyBean root) throws BuddiesCtlException{
		if (!user.isActive()) {
			throw new BuddiesCtlException("The user isn't an active.");
		}
		
		if (user.getRootGroupId() != root.getBuddyId())
			throw new BuddiesCtlException("The root group isn't correct.");
		if (getUsers().containsKey(user.getUserId()))
			throw new BuddiesCtlException("The user has exist.");
		if (getBuddies().containsKey(root.getBuddyId()))
			throw new BuddiesCtlException("The root buddy has exist.");

		TreeSet<Long> subSet = new TreeSet<Long>();
		subSet.add(root.getBuddyId());
		TreeSet<Long> shadowsSet = new TreeSet<Long>();
		UserDecorator userItem = new UserDecorator (user,subSet, shadowsSet);

		
		getUsers().put(user.userId, userItem);
		TreeSet<Long> rootSet = new TreeSet<Long>();
		BuddyDecorator buddyItem = new BuddyDecorator (root, rootSet);
		getBuddies().put(root.getBuddyId(), buddyItem);

		getUsersIndex().put(user.getLoginName(), user.getUserId());
		getBuddiesIndex().put(root.getAlias(), root.getBuddyId());
	}
	
	/**
	 *
	 * Operations on context after delete a user.
	 * @param userId User id.
	 * @throws org.grnt.buddy.BuddiesTreeControler.BuddiesCtlException
	 */
	public static void delUser(Long userId) throws BuddiesCtlException{
		Buddies buddies = getBuddies();
		Users users = getUsers();
		UsersIndex usersIndex = getUsersIndex();
		BuddiesIndex buddiesIndex = getBuddiesIndex();

		UserDecorator userDec = users.get(userId);
		if (!userDec.getUserBean().isActive()){
			throw new BuddiesCtlException("The user isn't an active.");
		}
		
		String loginName = userDec.getUserBean().getLoginName();

		TreeSet<Long> subSet = userDec.getSubBuddies();
		for (Long buddyId: subSet) {
			buddiesIndex.remove(absAlias(buddyId));
		}

		for (Long buddyId: subSet) {
			buddies.remove(buddyId);
		}

		TreeSet<Long> shadowsSet = users.get(userId).getShadows();
		for (Long shadowId : shadowsSet) {
			buddies.remove(shadowId);
		}

		usersIndex.remove(loginName);
		userDec.getUserBean().kill();
	}

	/**
	 *
	 * Make a UserBean object in context inactived.
	 * @param userId User id.
	 */
	public static void killUser(Long userId){
		Users users = getUsers();
		if(!users.containsKey(userId)) return;
		UserBean user = users.get(userId).getUserBean();
		if(!user.isActive()) return;
		user.kill();
	}

	/**
	 *
	 * Modify a UserBean's object's password field. And the object is in context.
	 * @param userId User id.
	 * @param password New password.
	 * @param committed Ask object to auto committed modify if it is ture.
	 * @throws org.grnt.buddy.BuddiesTreeControler.BuddiesCtlException
	 */
	public static void chPassword(Long userId, String password, boolean committed) throws BuddiesCtlException {
		UserBean user = getUsers().get(userId).getUserBean();
		if (!user.isActive()) return;
		try {
			user.setPassword(password, committed);
		} catch (OptStateException ex) {
			Logger.getLogger(BuddiesTreeControler.class.getName()).log(Level.SEVERE, null, ex);
			throw new BuddiesCtlException(ex);
		}
	}

	/**
	 *
	 * UserBean obeject commit the update.
	 * @param userId User Id
	 * @throws org.grnt.buddy.BuddiesTreeControler.BuddiesCtlException
	 */
	public static void userCommitCh(Long userId) throws BuddiesCtlException{
		UserBean user = getUsers().get(userId).getUserBean();
		if(!user.isActive()) return;
		try {
			user.commit();
		} catch (OptStateException ex) {
			Logger.getLogger(BuddiesTreeControler.class.getName()).log(Level.SEVERE, null, ex);
			throw new BuddiesCtlException(ex);
		}
	}

	/**
	 *
	 * UserBean obeject rollback the update.
	 * @param userId
	 * @throws org.grnt.buddy.BuddiesTreeControler.BuddiesCtlException
	 */
	public static void userRollbackCh(Long userId) throws BuddiesCtlException{
		UserBean user = getUsers().get(userId).getUserBean();
		if (!user.isActive()) return;
		try {
			user.rollback();
		} catch (OptStateException ex) {
			Logger.getLogger(BuddiesTreeControler.class.getName()).log(Level.SEVERE, null, ex);
			throw new BuddiesCtlException(ex);
		}
	}


	/**
	 *
	 * Operations on context after add a buddy.
	 * @param buddy Object of BuddyBean.
	 * @throws org.grnt.buddy.BuddiesTreeControler.BuddiesCtlException
	 */
	public static void addBuddy(BuddyBean buddy) throws BuddiesCtlException {
		Buddies buddies = getBuddies();
		Users users = getUsers();
		BuddiesIndex buddiesIndex = getBuddiesIndex();

		if (!buddy.isActive()) {
			throw new BuddiesCtlException(buddy.getChkMsg());
		}

		Long buddyId  = buddy.getBuddyId();
		Long userId   = buddy.getUserId();
		Long ownerId  = buddy.getOwner();
		Long parentId = buddy.getParentId();

		UserDecorator userItem = ((userId == null) || (userId == 0))? null: users.get(userId);
		UserDecorator ownerItem = users.get(ownerId);
		BuddyDecorator parentItem = buddies.get(parentId);

		if (userItem != null) {
			userItem.getShadows().add(buddyId);
		}
		ownerItem.getSubBuddies().add(buddyId);
		parentItem.getChildren().add(buddyId);
		buddies.put(buddyId, new BuddyDecorator(buddy, new TreeSet<Long>()));
		buddiesIndex.put(absAlias(buddyId), buddyId);
	}

	/**
	 *
	 * Travels on buddies tree.
	 * @param <R> return value.
	 * @param buddyId Buddy id.
	 * @param travelor Object of BuddiesTravelor.
	 * @param doElement Obejct of BuddiesTravelorFunction.
	 * @throws org.grnt.buddy.BuddiesTreeControler.BuddiesCtlException
	 */
	public static <R>void travelChildren(Long buddyId
			, BuddiesTravelor travelor
			, BuddiesTravelorFunction<R> doElement)throws BuddiesCtlException{
			travelor.setRoot(buddyId);
			travelor.travel(doElement);
	}

	/**
	 *
	 * Operations on context after delete a buddy.
	 * @param buddyId Buddy id.
	 * @throws org.grnt.buddy.BuddiesTreeControler.BuddiesCtlException
	 */
	public static void delBuddy(Long buddyId) throws BuddiesCtlException{
		Buddies buddies = getBuddies();
		Users users = getUsers();
		BuddiesIndex buddiesIndex = getBuddiesIndex();

		if (! buddies.containsKey(buddyId)) return;
		BuddyBean buddy = buddies.get(buddyId).getBuddyBean();
		/*
		 *I don't know wheter the follow will be done.
		 */
		/*
		if (!buddy.isActive()) 
			throw new BuddiesCtlException(buddy.getChkMsg());
		 */
		
		if (buddy.isGroup){
			for(Long childId : buddies.get(buddyId).getChildren()) {
				delBuddy(childId);
			}
		}

		Long userId   = buddy.getUserId();
		Long ownerId  = buddy.getOwner();
		Long parentId = buddy.getParentId();

		UserDecorator userItem  = (buddy.getIsGroup())? null: users.get(userId);
		UserDecorator ownerItem = getUsers().get(ownerId);
		BuddyDecorator parentItem = (parentId == null)? null: getBuddies().get(parentId);

		if (userItem != null) {
			userItem.getShadows().remove(buddyId);
		}
		if (parentItem != null) {
			parentItem.getChildren().remove(buddyId);
		}
		ownerItem.getSubBuddies().remove(buddyId);
		buddiesIndex.remove(absAlias(buddyId));
	}

	/**
	 *
	 * Operations on context after chanage a buddy's parent.
	 * @param buddyId Buddy id
	 * @param parentId Buddy id reference to current buddy's parent field. It represents
	 * the buddy will be change to.
	 * @param commited Unsensible.
	 * @throws org.grnt.buddy.BuddiesTreeControler.BuddiesCtlException
	 */
	public static void movBuddy(Long buddyId, Long parentId, boolean commited) throws BuddiesCtlException{
		final Buddies buddies = getBuddies();
		final BuddiesIndex buddiesIndex = getBuddiesIndex();
		
		BuddyBean buddy = null;

		if ((buddy = buddies.get(buddyId).getBuddyBean()) == null){
			return;
		}

		BuddyDecorator oldParentItem = null;
		BuddyDecorator newParentItem = null;

		if(!buddy.isActive()){
			throw new BuddiesCtlException(buddy.getChkMsg());
		}

		if ((oldParentItem = buddies.get(buddy.getParentId())) == null) {
			throw new BuddiesCtlException("The original buddy isn't in any buddy group.");
		}
		
		if ((newParentItem = buddies.get(parentId)) == null) {
			throw new BuddiesCtlException("The new parent can't be found with the parent id.");
		} else if (newParentItem.getBuddyBean().getOwner() != buddy.getOwner()) {
			throw new BuddiesCtlException("The parent with new parent id isn't " +
					"with in current user's community.");
		}

		final TreeSet<Long> childrenIds = new TreeSet<Long>();

		travelChildren (buddyId
				, new BuddiesForewordTravel()
				, new BuddiesTravelorFunction<Long>()
		{
			public Long doWith(BuddyDecorator element) {
				Long buddyId = element.getBuddyBean().getBuddyId();
				buddiesIndex.remove(absAlias(buddyId));
				childrenIds.add(buddyId);
				return buddyId;
			}
		});
		oldParentItem.getChildren().remove(buddyId);

//		buddy.setParentId(parentId, commited);
		buddy.setParentId(parentId, true);
		newParentItem.getChildren().add(buddyId);
		buddiesIndex.put(absAlias(buddyId), buddyId);
		for(Long childId: childrenIds) {
			buddiesIndex.put(absAlias(childId), childId);
		}
	}

	private final static void preDefLock(Long userId) {
		UserLocks uls = BuddiesContext.getUserLocks();
		if(! uls.containsKey(userId)) {
			uls.put(userId, null);
		}
		if (uls.get(userId).get() == null) {
			ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
			TwoTuple lockTuple = new TwoTuple<ReadLock, WriteLock>(rwl.readLock(), rwl.writeLock());
			WeakReference<TwoTuple<ReadLock, WriteLock>> weakRef =
					new WeakReference<TwoTuple<ReadLock, WriteLock>>(lockTuple);
			uls.put(userId, weakRef);
		}
	}

	/**
	 *
	 * Adds a read lock to a user. This action will be record in context.
	 * @param userId
	 */
	public static void readUser(Long userId){
		preDefLock (userId);
		UserLocks uls = BuddiesContext.getUserLocks();
		uls.get(userId).get().value1.lock();
	}

	/**
	 *
	 * Adds a write lock to a user. This action will be record in context.
	 * @param userId
	 */
	public static void writeUser(Long userId){
		preDefLock (userId);
		UserLocks uls = BuddiesContext.getUserLocks();
		uls.get(userId).get().value2.lock();
	}

	private final static void checkLock(Long userId)throws BuddiesCtlException {
		UserLocks uls = getUserLocks();
		if(! uls.containsKey(userId)) {
			throw new BuddiesCtlException("The user id" + userId +" isn't in map.");
		}
		TwoTuple<ReadLock, WriteLock> locks = uls.get(userId).get();
		if (locks == null) {
			throw new BuddiesCtlException("The lock of user id" + userId +" may be freed.");
		}
	}

	/**
	 *
	 * remove a read lock to a user. 
	 * @param userId
	 * @throws org.grnt.buddy.BuddiesTreeControler.BuddiesCtlException
	 */
	public static void readEndUser(Long userId) throws BuddiesCtlException{
		checkLock(userId);
		UserLocks uls = BuddiesContext.getUserLocks();
		uls.get(userId).get().value1.unlock();
	}

	/**
	 *
	 * remove a write lock to a user. 
	 * @param userId
	 * @throws org.grnt.buddy.BuddiesTreeControler.BuddiesCtlException
	 */
	public static void writeEndUser(Long userId) throws BuddiesCtlException{
		checkLock(userId);
		UserLocks uls = BuddiesContext.getUserLocks();
		uls.get(userId).get().value2.unlock();
	}
	
	/**
	 * It is a Exception class which thrown by BuddiesTreeControler object.
	 */
	public static class BuddiesCtlException extends Exception{

		/**
		 *
		 * @param cause
		 */
		public BuddiesCtlException(Throwable cause) {
			super(cause);
		}

		/**
		 *
		 * @param message
		 * @param cause
		 */
		public BuddiesCtlException(String message, Throwable cause) {
			super(message, cause);
		}

		/**
		 *
		 * @param message
		 */
		public BuddiesCtlException(String message) {
			super(message);
		}

		/**
		 *
		 */
		public BuddiesCtlException() {
		}
		
	}

}